﻿#pragma once

#include "CoreMinimal.h"
#include "VertexShader.h"

/**
 * 齐次裁剪空间裁剪相关算法实现
 */
class FClipSpaceCull
{
public:
	/**
	 * 判断点是不是属于裁剪面的面上或者外侧
	 */
	FORCEINLINE static bool Inside(const FVector4& Plane, const FVector4& Point)
	{
		return Dot4(Plane, Point) >= 0;
	}

	/**
	 * 插值获得边和平面的交点
	 */
	FORCEINLINE static FVertexToPixelInterpolants Intersect(const FVertexToPixelInterpolants& Point1, const FVertexToPixelInterpolants& Point2, const FVector4& Plane)
	{
		const float DA = Dot4(Point1.Position, Plane);
		const float DB = Dot4(Point2.Position, Plane);

		const float Weight = DA / (DA - DB);
		return FVertexToPixelInterpolants::LerpVertex(Point1, Point2, Weight);
	}

	/**
	 * 多边形和裁剪面执行裁剪计算
	 */
	FORCEINLINE static void ClipWithPlane(const FVector4& Plane, const TArray<FVertexToPixelInterpolants, TInlineAllocator<16>>& VertexList,
		TArray<FVertexToPixelInterpolants, TInlineAllocator<16>>& NewVertexList)
	{
		NewVertexList.Reset();
	
		for (int32 VertexIndex = 0, Count = VertexList.Num(); VertexIndex < Count; ++VertexIndex)
		{
			const FVertexToPixelInterpolants& CurVertex = VertexList[VertexIndex];
			const FVertexToPixelInterpolants& LastVertex = VertexList[(VertexIndex + VertexList.Num() - 1) % VertexList.Num()];

			if (Inside(Plane, CurVertex.Position))
			{
				if (!Inside(Plane,LastVertex.Position))
				{
					NewVertexList.Emplace(Intersect(LastVertex, CurVertex, Plane));
				}
			
				NewVertexList.Add(CurVertex);
			}
			else if (Inside(Plane, LastVertex.Position))
			{
				NewVertexList.Emplace(Intersect(LastVertex, CurVertex, Plane));
			}
		}
	}
	
	/**
	 * 在齐次空间使用Sutherland-Hodgeman裁剪算法裁剪三角形
	 */
	FORCEINLINE static void SutherlandHodgeman(TArray<FVertexToPixelInterpolants, TInlineAllocator<16>>& VertexList)
	{
		TArray<FVertexToPixelInterpolants, TInlineAllocator<16>> TempVertexList;
		
		// 采用的DirectX的左手坐标系统, 计算出来的NDC坐标范围为 X[-1, 1], Y[-1, 1], Z[0, 1]
		// OpenGL的NDC坐标范围为 X[-1, 1], Y[-1, 1], Z[-1, 1]
		// 如果是模拟OpenGL, NearClipPlane(0, 0, 1, 1);
		
		// 执行近裁剪面裁剪, 需要先执行近裁剪面的裁剪
		// 透视投影变换的的近裁剪面为 GNearClippingPlane = 10，这样点即便在视点上, w = 0, z = -10, dot4(NearClipPlane, Pos) < 0, 后续透视除法不会出现除0问题
		static FVector4 NearClipPlane(0, 0, 1, 0);
		ClipWithPlane(NearClipPlane, VertexList, TempVertexList);

		// 执行远裁剪面裁剪
		static FVector4 FarClipPlane(0, 0, -1, 1);
		ClipWithPlane(FarClipPlane, TempVertexList, VertexList);

		// 执行左裁剪面裁剪
		static FVector4 LeftClipPlane(1,0,0,1);
		ClipWithPlane(LeftClipPlane, VertexList, TempVertexList);

		// 执行右裁剪面裁剪
		static FVector4 RightClipPlane(-1,0,0,1);
		ClipWithPlane(RightClipPlane, TempVertexList, VertexList);

		// 执行顶裁剪面裁剪
		static FVector4 TopClipPlane(0,-1,0,1);
		ClipWithPlane(TopClipPlane, VertexList, TempVertexList);

		// 执行底裁剪面裁剪
		static FVector4 BottomClipPlane(0,1,0,1);
		ClipWithPlane(BottomClipPlane, TempVertexList, VertexList);
	}
};
